package net.jalisq.builder;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import net.jalisq.builder.joins.IJoin;
import net.jalisq.builder.joins.IJoinType;
import net.jalisq.builder.joins.JoinType;
import net.jalisq.builder.joins.Joinable;
import net.jalisq.builder.joins.On;
import net.jalisq.builder.joins.OnJoin;
import net.jalisq.builder.joins.OnJoinType;
import net.jalisq.builder.joins.Using;
import net.jalisq.builder.joins.UsingJoin;
import net.jalisq.builder.joins.UsingJoinType;
import net.jalisq.functions.Operation;
import net.jalisq.types.Expression;
import net.jalisq.types.IColumn;
import net.jalisq.types.PhysicalColumn;
import net.jalisq.types.Table;

import wulfUtils.template.StringTemplate;

public class Select implements Valid{
	
	private class FromImpl implements From {
		public JoinOrCond from(Table[] tabs) {
			Select.this.fromTable = tabs[0];
			/** exploit that comma and inner joins are equivalent **/
			int i = 0;
			for(Table tab : tabs ) {
				if( i != 0) Select.this.joiner.INNER_JOIN(tab);
				i++;
			}
			return jc;
		};
		
		
		
	}
		
	private From from = new FromImpl();
	 
	/** select column list **/ 
	private List<IColumn> columns = new LinkedList<IColumn>();
	
	/** table in the select **/
	private Table fromTable;
	/** tables to be joined by comma **/
	private List<Table> innerJoinNoCond = new LinkedList<Table>();
	
	/** list of all joins **/
	private List<IJoin> joins = new LinkedList<IJoin>();
	
	/** list of all where conditions **/
	private List<Condition> conditions = new LinkedList<Condition>();
	
	/** 
	 * Hidden constructor
	 */
	private Select(IColumn ... cols) {
		for(IColumn col : cols) {
			columns.add(col);
		}
		
	}
	
	public static From select(IColumn ... cols) {
		return new Select(cols).from;
	}
	
	
	
	
	public Select where(Condition c) {
		conditions.add(c);
		return this;
	}
	public Select add(Condition c) {
		return where(c);
	}
	
	public final String toString() {
		return getClass() + "!"  + hashCode() + " !!! intentionally left blank! Use toSQL() to obtain the SQL !!!";
		
	}
	
	@Override
	public String toSQL() {
		try{
			StringTemplate st = StringTemplate.loadTemplate("wulfUtils.linq.builder.templates.select", "sql", "%");
			st.put("columns", columns);
			st.put("tables", new LinkedList<Table>(){{
				add(fromTable);
				addAll(innerJoinNoCond);
			}});
			//System.out.println(vaCoBu.getRepr());
			st.put("condition", vaCoBu);
			return st.fillTemplate();
			
		} catch(Exception e) {
			throw new RuntimeException(e);
		}
		
	}
	
	private ValidCondBuilder vaCoBu = (ValidCondBuilder) Proxy.newProxyInstance(
			 ValidCondBuilder.class.getClassLoader(),
			 new Class[] { ValidCondBuilder.class},
			 	new InvocationHandler() {
				 	private CondBuilder condBuilder = null;
				 	public Object invoke(Object proxy, Method method, final Object[] args) {
				 		if( condBuilder == null) {
				 			condBuilder = new DefaultCondBuilder(condition);
				 		}
				 		Exception exp;
				 		try{
							return method.invoke(condBuilder,args);
						} catch ( Exception e) {
							exp = e;
						}
						try{
							return method.invoke(Select.this);
						} catch ( Exception e) {
							exp = e;
						}
						throw new IllegalArgumentException("No handler for " + method,exp);
				 	}
			 }
			 
	);

	private Condition condition;
	private Cond cond = new Cond() {
		 public ValidCondBuilder where(final Condition cond) {
			 condition = cond;
			 return vaCoBu; 
			 
		 };
	 };
	 
	 private JoinOrCond jc = (JoinOrCond) Proxy.newProxyInstance(
			 JoinOrCond.class.getClassLoader(),
            new Class[] { JoinOrCond.class },
            new InvocationHandler() {
				 public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
					try{
						
						return method.invoke(cond,args);
					} catch ( Exception e) {
						// TODO: handle exception
					}
					try{
						return method.invoke(joiner,args);
					} catch ( Exception e) {
						// TODO: handle exception
					}
					try{
						return method.invoke(Select.this);
						
					} catch ( Exception e) {
						// TODO: handle exception
					}
					throw new IllegalArgumentException("No handler for " + method);
				}
			 }
           
   );
	private Joinable joiner = (Joinable) Proxy.newProxyInstance(
			 Joinable.class.getClassLoader(),
           new Class[] { Joinable.class },
           new InvocationHandler() {
				 public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
					Object ret =  Select.this.jc;
					//System.out.println(method.getName() + " " + args[]);
					if( args.length == 1) {
						JoinType type = JoinType.valueOf(method.getName().toUpperCase());
						
						/** 
						 * special case to make sure that inner joins with no args simply get
						 * joined by comma
						 */
						if( JoinType.INNER_JOIN == type) {
							innerJoinNoCond.add((Table) args[0]);
							return ret;
						}
						IJoin join = type.makeJoin(Select.this);
						joins.add(join);
						return ret;
					}
					if( args.length == 2) {
						if( args[1] instanceof On) {
							OnJoinType type = OnJoinType.valueOf(method.getName().toUpperCase());
							IJoin join = type.makeJoin(Select.this, (On) args[1]);
							joins.add(join);
							return ret;
						} else if( args[1] instanceof Using) {
							UsingJoinType type = UsingJoinType.valueOf(method.getName().toUpperCase());
							IJoin join = type.makeJoin(Select.this, (Using) args[1]);
							joins.add(join);
							return ret;
							
						}
							
					}
					throw new IllegalArgumentException("No handler for " + method);
					
				}
				
	}); 
	 
	
	

}
